package com.bianmaba.spring.data.jpa.repository.support;

import com.bianmaba.spring.data.jpa.domain.PageableRequest;
import com.bianmaba.spring.data.jpa.domain.annotations.QueryCacheable;
import com.bianmaba.spring.data.jpa.domain.specification.DynamicSpecification;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.SQLQuery;
import org.hibernate.query.criteria.internal.compile.CriteriaQueryTypeQueryAdapter;
import org.hibernate.query.internal.NativeQueryImpl;
import org.hibernate.query.internal.QueryImpl;
import org.hibernate.transform.Transformers;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.lang.Nullable;

import javax.persistence.*;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * Created by cwx183898 on 2017/8/15.
 */
@NoRepositoryBean
public class ExtendedRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements ExtendedRepository<T, ID> {

    private static final Log LOG = LogFactory.getLog(ExtendedRepositoryImpl.class);

    public EntityManager entityManager;

    @Override
    public EntityManager getEntityManager() {
        return entityManager;
    }

    public ExtendedRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
        super(domainClass, entityManager);
        this.entityManager = entityManager;
    }

    @Override
    public Page<T> findAll(Specification<T> spec, Pageable pageable) {
        return super.findAll(spec, pageable);
    }

    public ExtendedRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.entityManager = entityManager;
    }

    @Override
    public List<T> findAllDistinctToList(String property, Object val) {
        return findAllDistinctToList(property, val, null);
    }

    @Override
    public List<T> findAllDistinctToList(String property, Object val, Pageable pageable) {
        LinkedHashMap params = new LinkedHashMap(0);
        if (property != null) {
            params.put(property, val);
        }
        return findAllDistinctToList(params, pageable);
    }

    @Override
    public Page<T> findAllDistinct(String property, Object val) {
        return findAllDistinct(property, val, null);
    }

    @Override
    public Page<T> findAllDistinct(String property, Object val, Pageable pageable) {
        LinkedHashMap params = new LinkedHashMap(0);
        if (property != null) {
            params.put(property, val);
        }
        return findAllDistinct(params, pageable);
    }


    @Override
    public Page<T> findAllDistinct(final Map<String, Object> params, Pageable pageable) {
        if (pageable == null) {
            pageable = PageRequest.of(0, Integer.MAX_VALUE);
        }
        Specification spec = new DynamicSpecification(params, pageable, true);
        TypedQuery<T> query = getQuery(spec, pageable);
        Page<T> page = readPage(query, getDomainClass(), pageable, spec);
        return page;
    }

    @Override
    public List<T> findAllDistinctToList(final Map<String, Object> params, Pageable pageable) {
        if (pageable == null) {
            pageable = PageRequest.of(0, Integer.MAX_VALUE);
        }
        Specification spec = new DynamicSpecification(params, pageable, true);
        TypedQuery<T> query = getQuery(spec, pageable);
        query.setFirstResult((int) pageable.getOffset());
        query.setMaxResults(pageable.getPageSize());
        return query.getResultList();
    }

    @Override
    public List<T> findAllToList(String property, Object val) {
        return findAllToList(property, val, null);
    }

    @Override
    public List<T> findAllToList(String property, Object val, Pageable pageable) {
        LinkedHashMap params = new LinkedHashMap(0);
        if (property != null) {
            params.put(property, val);
        }
        return findAllToList(params, pageable);
    }

    @Override
    public Page<T> findAll(String property, Object val) {
        return findAll(property, val, null);
    }

    @Override
    public Page<T> findAll(String property, Object val, Pageable pageable) {
        LinkedHashMap params = new LinkedHashMap(0);
        if (property != null) {
            params.put(property, val);
        }
        return findAll(params, pageable);
    }

    @Override
    public Page<T> findAll(final Map<String, Object> params, Pageable pageable) {
        if (pageable == null) {
            pageable = PageRequest.of(0, Integer.MAX_VALUE);
        }
        Specification spec = new DynamicSpecification(params, pageable);
        TypedQuery<T> query = getQuery(spec, pageable);
        Page<T> page = readPage(query, getDomainClass(), pageable, spec);
        return page;
    }

    @Override
    public List<T> findAllToList(final Map<String, Object> params, Pageable pageable) {
        if (pageable == null) {
            pageable = PageRequest.of(0, Integer.MAX_VALUE);
        }
        Specification spec = new DynamicSpecification(params, pageable);
        TypedQuery<T> query = getQuery(spec, pageable);
        query.setFirstResult((int) pageable.getOffset());
        query.setMaxResults(pageable.getPageSize());
        return query.getResultList();
    }


    @Override
    public List<T> executeSqlQuery(String sql) {
        return this.executeSqlQuery(sql, null, null, null, null);
    }


    @Override
    public List executeSqlQuery(String sql, Object[] params) {
        return this.executeSqlQuery(sql, params, null, null, null);
    }

    /**
     * @param sql         sql
     * @param entityClass 自定义类型或Map、List的class
     * @param <E>         自定义类型或Map、List
     * @return
     */
    @Override
    public <E> List<E> executeSqlQuery(String sql, Class<E> entityClass) {
        return this.executeSqlQuery(sql, null, null, null, entityClass);
    }


    /**
     * @param sql         sql
     * @param params      参数列表
     * @param entityClass 自定义类型或Map、List的class
     * @param <E>         自定义类型或Map、List
     * @return
     */
    @Override
    public <E> List<E> executeSqlQuery(String sql, Object[] params, Class<E> entityClass) {
        return this.executeSqlQuery(sql, params, null, null, entityClass);

    }

    /**
     * @param sql         sql
     * @param params      参数列表
     * @param firstResult 起始记录数
     * @param maxResults  返回记录数
     * @param entityClass 自定义类型或Map、List的class
     * @param <E>         自定义类型或Map、List
     * @return
     */
    @Override
    public <E> List<E> executeSqlQuery(String sql, Object[] params, Integer firstResult, Integer maxResults, Class<E> entityClass) {
        Query query = createQuery(sql, entityClass);
        bindParams(params, query);
        bindPageable(firstResult, maxResults, query);
        return query.getResultList();
    }

    /**
     * @param sql         sql
     * @param params      参数列表
     * @param entityClass 自定义类型或Map、List的class
     * @param <E>         自定义类型或Map、List
     * @return
     */
    @Override
    public <E> E executeSqlQueryUniqueResult(String sql, Object[] params, Class<E> entityClass) {
        List<E> list = executeSqlQuery(sql, params, entityClass);
        if (!list.isEmpty()) {
            return list.get(0);
        }
        return null;
    }

    @Override
    public Object executeSqlQuerySingleResult(String sql, Object[] params) {
        Query query = createQuery(sql, null);
        bindParams(params, query);
        return query.getSingleResult();
    }


    @Override
    public Integer executeSqlUpdate(String sql) {
        return this.executeSqlUpdate(sql, null);
    }

    @Override
    public Integer executeSqlUpdate(String sql, Object[] params) {
        Query query = getEntityManager().createNativeQuery(sql, Integer.class);
        bindParams(params, query);
        return query.executeUpdate();
    }


    private void bindPageable(Integer firstResult, Integer maxResults, Query query) {
        if (firstResult != null) {
            query.setFirstResult(firstResult);
        }
        if (maxResults != null) {
            query.setMaxResults(maxResults);
        }
    }

    private void bindParams(Object[] params, Query query) {
        if (params != null) {
            for (int i = 1; i <= params.length; i++) {
                query.setParameter(i, params[i - 1]);
            }
        }
    }

    @Override
    protected <S extends T> TypedQuery<S> getQuery(@Nullable Specification<S> spec, Class<S> domainClass, Sort sort) {
        TypedQuery query = super.getQuery(spec, domainClass, sort);
        //根据泛型中实体的@Cacheable注解确定该实体是否支持查询缓存
        boolean cacheable = cacheable();
        //JPA下配置是否启用二级缓存
        query.setHint("org.hibernate.cacheable", cacheable);
        return query;
    }


    private <E> Query createQuery(String sql, Class<E> entityClass) {
        Query query = null;
        if (entityClass != null) {
            if (entityClass.isAssignableFrom(List.class) || entityClass.isAssignableFrom(ArrayList.class)) {
                query = getEntityManager().createNativeQuery(sql);
                query.unwrap(SQLQuery.class).setResultTransformer(Transformers.TO_LIST);
            } else if (entityClass.isAssignableFrom(Map.class) || entityClass.isAssignableFrom(HashMap.class)) {
                query = getEntityManager().createNativeQuery(sql);
                query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
            } else {
                query = getEntityManager().createNativeQuery(sql, entityClass);
                query.setHint("org.hibernate.cacheable", cacheable(entityClass));
            }
        } else {
            query = getEntityManager().createNativeQuery(sql);
        }
        return query;
    }

    private boolean cacheable() {
        Class clazz = getDomainClass();
        Annotation annotation = clazz.getAnnotation(QueryCacheable.class);
        if (annotation != null) {
            QueryCacheable cacheable = (QueryCacheable) annotation;
            boolean queryCacheable = cacheable.value();
            return queryCacheable;
        }
        return false;
    }

    private boolean cacheable(Class clazz) {
        Annotation annotation = clazz.getAnnotation(Cacheable.class);
        if (annotation != null) {
            Cacheable cacheable = (Cacheable) annotation;
            return cacheable.value();
        }
        return false;
    }

    @Override
    public void clear() {
        entityManager.clear();
    }

    @Override
    public void close() {
        entityManager.close();
    }

    @Override
    public void detach(Object obj) {
        entityManager.detach(obj);
    }

    @Override
    public void refresh(Object obj) {
        entityManager.refresh(obj);
    }
}
